package beer.zivelin.awesome.walawala.mobile.dao;

import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.Log;

import com.google.common.base.Stopwatch;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

import androidx.annotation.Nullable;
import beer.zivelin.awesome.walawala.mobile.entity.ApplicationData;
import beer.zivelin.awesome.walawala.mobile.entity.ApplicationDataPO;
import beer.zivelin.awesome.walawala.mobile.service.T9;

public class ApplicationStore extends SQLiteOpenHelper {
    public static final String TAG = "ApplicationStore";
    public static final String TABLE_NAME = "applications";
    public static final String DB_NAME = "applications.db";
    public static final int DB_VERSION = 4;
    private Map<String, Drawable> iconCache;
    private Context context;

    boolean newer = false;

    public ApplicationStore(@Nullable Context context) {
        super(context, DB_NAME, null, DB_VERSION);
        iconCache = new HashMap<>();
        this.context = context;
    }

    public void open() {
        getWritableDatabase();
    }


    public void close() {
        super.close();
        getWritableDatabase().close();
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        final String sql = "create table " + TABLE_NAME + "(package_name text, t9_pinyin text, name text, icon_name text, icon blob)";
        db.execSQL(sql);
        newer = true;
        Log.d(TAG, "on create, is newer?" + isNewer());
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        final String sql = "drop table " + TABLE_NAME;
        db.execSQL(sql);
        onCreate(db);
        Log.d(TAG, "on upgrade");
    }

    public boolean isNewer() {
        return newer;
    }

    public Set<String> selectPackages() {
        Set<String> packages = new HashSet<>();
        Cursor cursor = getWritableDatabase().query(
                TABLE_NAME,
                new String[]{"distinct(package_name)"},
                null,
                null,
                null,
                null,
                null);
        if (cursor.moveToFirst()) {
            do {
                packages.add(cursor.getString(0));
            } while (cursor.moveToNext());
        }
        cursor.close();
        return packages;
    }

    @SuppressLint("Range")
    public List<ApplicationData> selectAll() {
        Stopwatch stopwatch = Stopwatch.createStarted();

        List<ApplicationData> applicationDataList = new LinkedList<>();
        Cursor cursor = getWritableDatabase().query(
                TABLE_NAME,
                new String[]{"package_name", "t9_pinyin", "name", "icon_name"},//, "icon"},
                null,
                null,
                null,
                null,
                null);

        if (cursor.moveToFirst()) {
            do {
                ApplicationData data = new ApplicationData();
                data.setPackageName(cursor.getString(cursor.getColumnIndex("package_name")));
                data.setName(cursor.getString(cursor.getColumnIndex("name")));
                loadIcon(data);

                applicationDataList.add(data);
            } while (cursor.moveToNext());
        }
        cursor.close();

        Log.w(TAG, "query used " + stopwatch.elapsed(TimeUnit.MILLISECONDS) + " ms");

        return applicationDataList;
    }


    public long delete(String packageName) {
        long ret = getWritableDatabase().delete(TABLE_NAME, "package_name=?", new String[]{packageName});

        Log.v(TAG, "delete " + packageName + ", count: " + ret);
        return ret;
    }

    public long deleteAll() {
        return getWritableDatabase().delete(TABLE_NAME, "1=1", null);
    }

    public boolean contains(String packageName) {
        Cursor cursor = getWritableDatabase().query(
                TABLE_NAME,
                new String[]{"package_name"},
                "package_name = ?",
                new String[]{packageName},
                null,
                null,
                null);
        boolean hasPackage = cursor.moveToFirst();
        cursor.close();
        return hasPackage;
    }


    @SuppressLint("Range")
    public List<ApplicationData> searchByT9(String t9) {
        List<ApplicationData> applicationDataList = new LinkedList<>();

        //用于结果去重
        Set<String> applicationPackages = new HashSet<>();

        Cursor cursor = getWritableDatabase().query(
                TABLE_NAME,
                new String[]{"package_name", "t9_pinyin", "name", "icon_name"},
                "t9_pinyin like ?",
                new String[]{"%" + t9 + "%"},
                null,
                null,
                "name asc");

        if (cursor.moveToFirst()) {
            do {
                String packageName = cursor.getString(cursor.getColumnIndex("package_name"));
                if (!applicationPackages.contains(packageName)) {
                    ApplicationData data = new ApplicationData();
                    data.setPackageName(packageName);
                    data.setName(cursor.getString(cursor.getColumnIndex("name")));
                    this.loadIcon(data);

                    applicationDataList.add(data);
                    applicationPackages.add(packageName);
                }
            } while (cursor.moveToNext());
        }

        cursor.close();

        return applicationDataList;
    }

    /**
     * 依次从缓存、本地文件和PackageManager中获取packageName对应的图标文件，并将图标放入data.icon中
     *
     * @param data
     */
    public void loadIcon(ApplicationData data) {
        Drawable icon = loadFromCache(data);
        if (icon != null) {
            data.setIcon(icon);
            return;
        }

        try {
            icon = loadFromLocal(data);
            if (icon != null) {
                data.setIcon(icon);
                saveToCache(data);
                return;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            icon = loadFromPM(data);
            if (icon != null) {
                data.setIcon(icon);
                saveToLocal(data);
                saveToCache(data);
            }
        } catch (IOException | PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }

    }

    public void batchImport(List<ApplicationDataPO> dataList, boolean deleteAll) {
        SQLiteDatabase db = getWritableDatabase();
        db.beginTransaction();

        if (deleteAll) {
            this.deleteAll();
        }

        try {
            if (dataList != null) {
                dataList.forEach(app -> {
                    insert(app);
                });
            }
            db.setTransactionSuccessful();
        } catch (Exception e) {

        } finally {
            db.endTransaction();
        }
    }

    public void batchImport(List<ApplicationDataPO> toInsert, Set<String> toDelete) {
        SQLiteDatabase db = getWritableDatabase();
        db.beginTransaction();

        try {
            if (toInsert != null) {
                toInsert.forEach(app -> {
                    insert(app);
                });
            }
            if (toDelete != null) {
                toDelete.forEach(packageName -> {
                    boolean isDel = removeLocalIcon(packageName);
                    delete(packageName);
                    Log.e(TAG, "delele icon of " + packageName + ": " + isDel);
                });
            }
            db.setTransactionSuccessful();
        } catch (Exception e) {

        } finally {
            db.endTransaction();
        }
    }

    public long insert(ApplicationDataPO po) {
        ApplicationData data = po.getData();
        if (data.getIcon() == null) {
            this.loadIcon(data);
        }
        ContentValues values = new ContentValues();
        values.put("package_name", data.getPackageName());
        values.put("t9_pinyin", po.getT9Pinyin());
        values.put("name", data.getName());
        values.put("icon_name", data.getIconName());
        long ret = getWritableDatabase().insert(TABLE_NAME, null, values);

        Log.v(TAG, "insert " + data.getName() + ", count: " + ret);

        return ret;
    }


    static Bitmap drawableToBitmap(Drawable drawable) {
        // 获取 drawable 长宽
        int width = drawable.getIntrinsicWidth();
        int heigh = drawable.getIntrinsicHeight();

        drawable.setBounds(0, 0, width, heigh);

        // 获取drawable的颜色格式
        Bitmap.Config config = drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888
                : Bitmap.Config.RGB_565;
        // 创建bitmap
        Bitmap bitmap = Bitmap.createBitmap(width, heigh, config);
        // 创建bitmap画布
        Canvas canvas = new Canvas(bitmap);
        // 将drawable 内容画到画布中
        drawable.draw(canvas);
        return bitmap;
    }

    private Drawable loadFromPM(ApplicationData data) throws PackageManager.NameNotFoundException {
        try {
            PackageInfo packageInfo = context.getPackageManager().getPackageInfo(data.getPackageName(), PackageManager.GET_META_DATA);
            if (packageInfo != null) {
                Log.d(TAG, "load icon from package-manager");
                return packageInfo.applicationInfo.loadIcon(context.getPackageManager());
            } else {
                return null;
            }
        } catch (PackageManager.NameNotFoundException e) {
            Log.e(TAG, "load icon error: " + data.getPackageName(), e);
            throw e;
        }
    }

    private Drawable loadFromLocal(ApplicationData data) throws IOException {
        File iconFile = new File(context.getFilesDir().getPath() + "/" + data.getIconName());
        if (iconFile.exists()) {
            FileInputStream in = null;
            try {
                in = context.openFileInput(data.getIconName());
                Bitmap bmp = BitmapFactory.decodeStream(in);
                BitmapDrawable icon = new BitmapDrawable(null, bmp);
                return icon;
            } finally {
                if (in != null) {
                    in.close();
                }
            }
        }
        return null;
    }

    private boolean removeLocalIcon(String packageName) {
        File iconFile = new File(context.getFilesDir().getPath() + "/" + packageName + ".png");
        if (iconFile.exists()) {
            return iconFile.delete();
        }
        return true;
    }

    private void saveToLocal(ApplicationData data) throws IOException {
        File iconFile = new File(context.getFilesDir().getPath() + "/" + data.getIconName());
        if (!iconFile.exists()) {
            iconFile.createNewFile();
        }
        FileOutputStream out = context.openFileOutput(iconFile.getName(), Context.MODE_PRIVATE);
        Bitmap bmp = drawableToBitmap(data.getIcon());
        bmp.compress(Bitmap.CompressFormat.PNG, 100, out);
        out.close();
    }

    private Drawable loadFromCache(ApplicationData data) {
        return this.iconCache.get(data.getPackageName());
    }

    private void saveToCache(ApplicationData data) {
        this.iconCache.put(data.getPackageName(), data.getIcon());
    }

}
